查看原文
其他

记一次 CVE-2018-8373 利用构造过程

银雁冰 看雪学院 2019-05-26

CVE-2018-8373 是趋势科技上个月抓到的一个 vbscript UAF 0day,它是前几个月 CVE-2018-8174 的兄弟漏洞。由于趋势的博文已经把漏洞原理和整个利用过程讲得十分清楚,所以看完文章后的我只有一个问题:如何写一个 exp?


趋势公布的 hash 在 VT 上是没有的,而且文章中只给了 PoC 和部分高度混淆的在野利用片段, 出于这个原因, 我想自己构造一个 exp, 供自己和组内的小伙伴分析调再使用。

由于这个漏洞的触发原理有一点点 0189 的影子,又有一点 8174 的影子,利用部分还有一点点 6332 的影子。所以在熟悉前 3 个漏洞的前提下,写出这个漏洞的 exp 应该不难, 于是我白天花了几个小时试着写了个 exp。

鉴于此漏洞潜在的危害性, 完整代码不予公开,本文仅记录构造过程中的一些细节。


UAF 的原理趋势已经讲的很清楚了, 由于从页堆信息来看,每个二维数组的 tagSAFEARRAY结构占内存空间为 0x30(实际的 tagSAFEARRAY 前面还多申请了 0x10 字节)。 可以仔细看趋势的截图,或者自己动手做一下。


所以为了顺利 UAF,我们需要设计 pvData 部分大小为 0x30 的一维数组,也就是有 3 个元素的一维数组,令其为 array(2)。

我们对照趋势的图 4/图 11, Get P 回调中对 array 数组的大小进行了更改:
array(2)->array(100000),随后利用 array(i) = array2 循环申请大量小空间的二维数组 array2, array2 被定义为 array2(0, 6)。 此过程需要申请大量二维数组的tagSAFEARRAY 结构体,其中有一个会占用原来 array 的 pvAata 内存,并在 Get P 返回之后将 tagSAFEARRAY. rgsabound[1].cElement 覆盖为 0x0fffffff。

所以下一步就是找出哪个 array2 的头部占用了原来 array 的 pvAata 内存, 这个过程可以参考趋势图 16。

思路其实很简单,我们遍历新的 array 数组,找出其中的一维大小为 0x0ffffffe 的 array2。

我们假设这个 array2 在 array 中对应的下标为 index_vul。如下:


找到 array2 之后,我们可以用 array(index_vul)(a, b)来索引这个数组,其中 a∈[0,0xffffffe], b∈[0,1]。这个数组可以越界读写 array(index_vul)(a, b).pvData开始的一大片内存。


现在我们拥有一个可以越界读写的数组,最终目的是要去实现任意地址读写。下一步如何进行?


从趋势的图 15/图 16 可以看到,这个漏洞借助了和 6332 一样的思路来进行利用,即借助大量连续内存申请来实现多个 array2.pvData 内存的连续,每个 array2.pvData 中间间隔 8 字节的堆数据。我们利用越界读写能力通过对 8 字节的错位进行赋值来改变 variant变量在内存中的含义,从而构造一个基地址为 0,元素大小为 1,元素个数为 0x7ffffff 的一维数组;同时再泄露一块内存的地址供读写使用。从而实现任意地址读写。


现在的问题是: 如何找出趋势图 14/15 中的 index_a 和 index_b?关于这点,原文中说得含糊其辞,而且从混淆的片段(图 14)看去步骤较为复杂,还涉及到部分没有出现在代码片段中的函数。


我在放弃理解图 14 的代码片段后,转而开
始思考有没有简单一点的办法。从趋势图 15 们可以看到 index_a index_b 所需要满足的位置。调试器中,我们可以看到 index_a 有如下特征。


可以看到 array(index_vul)(index_a-8, 0)至 array(index_vul)(index_a-1, 0)都存储着短整型的 3 对应的 variant 结构,到了 array(index_vul)(index_a, 0),由于堆结构数据,错位了 8 个字节,导致 array(index_vul)(index_a, 0)代表的 variant 和前面后不一样。到了 array(index_vul)(index_a+1, 0)。由于我们之前给每个 array2 元素都设置为 3,见趋势图 10。所以 array(index_vul)(index_a+1, 0)开始代表的都是存储着长整型 2 对应的 variant 结构。


VB 有一个函数 VarType,可以用来判断 variant 的类型:


我们可以借助其实现如下逻辑:


这样我们就找到了满足条件的 index_a


现在我们已经找到了 index_a,如何找到它对应的 index_b 呢?这个则更简单,我们可以利用越界写将 array(index_vul)(index_a, 0)设置为一段字符串,例如“AAAA”,这样某一个 array2 Data 域就变成了 8(2 字节)。我们遍历 array 数组,找出array(index_b)(0, 0)=8 index_b 即可,如下:


当然,上述做法存在一定的失败率,我相信原 exp 中的校验一定会更保险。但是这样的 exp对于本地调试来说,足够了。


在取得 index_a index_b 之后。我们就得到了两个可以用来进行类型混淆的 variant 构。后续就是借助假的字符串数据和类型混淆构造一个超长一维数组了。


我们模仿 yuange 6332 利用代码,令 array(index_vul)(index_a, 0)处存储我们构造的字符串地址,令 array(index_vul)(index_a+2, 0)处存储我们构造的用来伪造数组的字符串地址。然后借助 array(index_b)(0, 0)array(index_b)(2, 0)去分别改动两处的类型,令 array(index_vul)(index_a, 0)处的 variant 转化为长整型,令array(index_vul)(index_a, 0)处的 variant 转化为数组,整个过程只需要如下几句代码。 从而获得一个超长数组和一个被泄露的字符串地址。


我们在调试器中看一下上述过程前后 array(index_vul)(index_a+2, 0)的数据变化。


rw_primit 第一句代码执行后:


执行 rw_primit 后:


现在,我们有了一个块泄露的内存 util_mem,一个可以实现 32 位下用户态任意地址读写的一维数组 array(index_vul)(index_a+2, 0),接下来就是利用这两个工具封装任意地址读写函数了。


任意地址读:


任意地址写是一样的原理,此处不再贴出。 相关原理在其他分析6332/0189/8174 的文章中已经有很多详细说明,此处不再提及。


借助上述封装好的函数,将 8174 的利用代码稍微改造一下就可以实现后续步骤:


当然 8174 这种利用方法并不能躲避 ROP 检测, 还是当年的 GodMode 比较经典。


anyway,经过几个小时的努力,我成功地在打了 2018 7 月份全补丁的 win7 x86 sp1 IE11 上弹了一个计算器:


CVE-2018-8174 CVE-2018-8373 这两个漏洞, 思路上基本是 yuange 的漏洞利用技+TK 的利用技术, 威力可见一斑。




看雪ID:银雁冰

bbs.pediy.com/user-246327


本文由看雪论坛 银雁冰  原创

转载请注明来自看雪社区





热门技术文章推荐:







戳原文,看看大家都是怎么说的?

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存